package yang.java8.streamAPI;

import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.IntSummaryStatistics;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import yang.java8.util.MyObject;
import yang.java8.util.Util;

@SuppressWarnings("unused")
public class StreamApi {
	
	public static void main(String[] args) {
		long now  = System.currentTimeMillis();
		now = Util.showTime(now, "start");
		List<MyObject> list = Util.generateMyObjectList(1000);
		now = Util.showTime(now, "after generateMyObjectList");
		streamFilter(list);
		now = Util.showTime(now, "streamFilter");
		normolCount(list);
		now = Util.showTime(now, "normolCount");
	}
	
	
	
	public static void streamFilter(List<MyObject> list) {
		long count = list.stream().parallel().peek(w -> System.out.println(w)).filter(w->w.toString().length() > 12).count();
		System.out.println(count);
		long count1 = list.parallelStream().peek(w -> System.out.println(w)).filter(w -> w.toString().length() > 12).count();
		System.out.println(count1);
	}
	
	public static void normolCount(List<MyObject> list) {
		long count = 0;
		for (MyObject o : list) {
			if (o.toString().length() > 12) count ++;
		}
		System.out.println(count);
	}
	
	public static void createStream() throws IOException {
		Stream<String> words = Stream.of("a,b,c".split(","));
		Stream<String> song = Stream.of("gently", "down", "the", "stream");
		Stream<String> array = Arrays.stream(new String[1]);
		Stream<String> silence = Stream.<String>empty();
		Stream<String> echos = Stream.generate(() -> "Echo");
		Stream<Double> randoms = Stream.generate(Math::random);
		
		Stream<BigInteger> integers = Stream.iterate(BigInteger.ZERO, n -> n.add(BigInteger.ONE));
		
		Stream<String> words1 = Pattern.compile("").splitAsStream(null);
		
		try (Stream<String> lines = Files.lines(null)) {
			// 对line操作  Stream支持AutoCloseable 
		}
		
	}
	
	public static void filter_map_flatMap() {
		List<String> list = Util.generateList(1000);
		Stream<String> words = list.stream();
		Stream<String> longWords = words.filter(w -> w.length() > 12);
		
		// map方法，会对每一个元素应用一个函数，返回值收集到一个新的流中
		Stream<String> lowercaseWords = words.map(String::toLowerCase);
		Stream<Character> firstChars = words.map(w -> w.charAt(0)); 
		
		Stream<Stream<Character>> result = words.map(s -> characterStream(s));
		Stream<Character> letters = words.flatMap(s -> characterStream(s));
		
	}
	
	/**
	 * 字符串流化
	 */
	public static Stream<Character> characterStream(String s) {
		List<Character> result = new ArrayList<>();
		for (char c : s.toCharArray()) {
			result.add(c);
		}
		return result.stream();
	}
	
	public static void limit_skip_concat_peek() {
		// 裁剪指定长度的流
		Stream<Double> randoms = Stream.generate(Math::random).limit(100);
		// 丢弃指定长度的元素
		Stream<Double> words = randoms.skip(10);
		// 连接两个流， 第一个流不应无限长，否则第二个流永远无机会被添加到后面
		Stream<Double> combined = Stream.concat(randoms, words);
		// peek 方法会产生一个原始流相同的元素，但每次获取前会调用一个函数，方便调试
		Object[] powers = Stream.iterate(1.0, p -> p * 2)
				.peek(e -> System.out.println("Fetching " + e)).limit(20).toArray();
		
	}
	
	/**
	 * 有状态的流，即每个元素必须记住之前的流
	 */
	public static void distinct_sorted() {
		// 去重， 该流必须记住之前读取的流，否则去重无法实现
		Stream<String> uniqueWords = Stream.of("merrily", "merrily", "merrily", "gently").distinct();
		Stream<String> longestFirst = uniqueWords.sorted(Comparator.comparing(String::length).reversed());
	}
	
	public static void aa() {
		Stream<String> words = Util.generateList(100).stream();
		Optional<String> largest = words.max(String::compareToIgnoreCase);
		if (largest.isPresent()) {
			System.out.println("largest: " + largest.get());
		}
		
		// 查找第一个(如果存在的话)
		Optional<String> startsWithQ = words.filter(s -> s.startsWith("Q")).findFirst();
		// 找到任意一个，并行时更有效
		startsWithQ = words.filter(s -> s.startsWith("Q")).findAny();
		startsWithQ = words.parallel().filter(s -> s.startsWith("Q")).findAny();
		// 只判断有没有, 全符合，没有一个符合，以下均可以并行
		boolean aWordStartWithQ = words.parallel().anyMatch(s -> s.startsWith("Q"));
		boolean allMatch = words.parallel().allMatch(s -> s.startsWith("Q"));
		boolean noneMatch = words.parallel().noneMatch(s -> s.startsWith("Q"));
	}
	
	
	public static void reduce() {
		Stream<String> stream = Util.generateList(100).stream();
		// 计算v1 + v2 + v3 + ....
		Optional<String> ret = stream.reduce((v1, v2) -> v1 + v2);
		// 使用不影响结果的初始值，防止空optional, 可直接获得结果
		String strRet = stream.reduce("", (v1, v2) -> v1 + v2);
		// 计算字符创长度
		int length = stream.reduce(0, (total, word) -> total + word.length(), (total, total2) -> total + total2);
		// 等价的简单写法
		length = stream.mapToInt(String::length).sum();
	}
	
	public static void collect() {
		Stream<String> stream = Util.generateList(100).stream();
		// 返回Object的数组，若需要String，则传入类型构造函数
		String[] result = stream.toArray(String[]::new);
		HashSet<String> set = stream.collect(HashSet::new, HashSet::add, HashSet::addAll);
		
		List<String> list = stream.collect(Collectors.toList());
		Set<String> set1 = stream.collect(Collectors.toSet());
		TreeSet<String> set2 = stream.collect(Collectors.toCollection(TreeSet::new));
		
		// 将所有的字符串收集起来
		String ret = stream.collect(Collectors.joining());
		// 元素中间添加个分隔符
		ret = stream.collect(Collectors.joining(", "));
		// 若流中包涵字符串以外的对象，则需要先转换
		ret = stream.map(Object::toString).collect(Collectors.joining(", "));
		
		IntSummaryStatistics summary = stream.collect(Collectors.summarizingInt(String::length));
		double averageWordLength = summary.getAverage();
		summary.getCount();
	}
	
	public static void toMap() {
		Stream<MyObject> myObj = Util.generateMyObjectList(100).stream();
		// 若多个元素拥有同一个键，那么会抛出IllegalStateException
		Map<Integer, String> indexValue = myObj.collect(Collectors.toMap(MyObject::getIndex, MyObject::getValue));
		Map<Integer, MyObject> indexMyObject = myObj.collect(Collectors.toMap(MyObject::getIndex, Function.identity()));
		
		// 手动处理冲突，只保留第一个
		Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());
		Map<String, String> languageNames = locales.collect(
				Collectors.toMap(
						l -> l.getDisplayLanguage(), 
						l -> l.getDisplayLanguage(l),
						(existingValue, newValue) -> existingValue));
		
		// 手动处理，将值保留在Set中，冲突时。用原有set新建新set,后来值加入原来set中
		Map<String, Set<String>> countryLanguageSets = locales.collect(
				Collectors.toMap(
						l -> l.getDisplayCountry(), 
						l -> Collections.singleton(l.getDisplayLanguage()),
						(a, b) -> {
							Set<String> r = new HashSet<>(a);
							r.addAll(b);
							return r;
						}));
		
		// 手动处理，指定TreeMap
		Map<Integer, MyObject> idToPerson = myObj.collect(
				Collectors.toMap(
						MyObject::getIndex, 
						Function.identity(),
						(existingValue, newValue) -> {throw new IllegalStateException(); },
						TreeMap::new ));
	}
	
	public static void groupingBy1() {
		Stream<Locale> locales = Stream.of(Locale.getAvailableLocales());
		Map<String, List<Locale>> countryToLocales = locales.collect(Collectors.groupingBy(Locale::getCountry));
		// 产生结果 [it_CH, de_CH, fr_CH]
		List<Locale> swissLocales = countryToLocales.get("CH");
		
		// 分组一组使用英语，一组不是
		Map<Boolean, List<Locale>> englishAndOtherLocales = locales.collect(Collectors.partitioningBy(l -> l.getLanguage().equals("en")));
		List<Locale> englishLocales = englishAndOtherLocales.get(true);
		
		//收集为set
		Map<String, Set<Locale>> countryToLocalSet = locales.collect(Collectors.groupingBy(Locale::getCountry, Collectors.toSet()));
		
		//counting计数
		Map<String, Long> countryToLocaleCounts = locales.collect(Collectors.groupingBy(Locale::getCountry, Collectors.counting()));
		
		Stream<MyObject> objs = Util.generateMyObjectList(100).stream();
		//summing
		Map<String, Integer> summing = objs.collect(Collectors.groupingBy(MyObject::getValue, Collectors.summingInt(MyObject::getIndex)));
		// maxBy or minBy TODO
		Map<String, Optional<MyObject>> max = objs.collect(Collectors.groupingBy(MyObject::getValue, Collectors.maxBy(Comparator.comparing(MyObject::getIndex))));
	}

}
